package cn.virens.common.mybatis.mapper.mybatis;

import cn.virens.common.mybatis.dynamic_table.DynamicTableNameFactory;
import cn.virens.common.util.exception.APIException;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.dromara.hutool.core.reflect.FieldUtil;

/**
 * Mybatis 拦截器，填充动态表名参数
 *
 * @author virens
 */
@Intercepts({//
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),//
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),//
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),//
})
public class MybatisDynamicTableNameInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        var mappedStatement = getMappedStatement(invocation.getArgs());
        var boundSql = getBoundSql(mappedStatement, invocation.getArgs());

        // 获取原始SQL & 修改SQL中的表名参数
        String oldSql = boundSql.getSql();
        String newSql = DynamicTableNameFactory.generate(oldSql);

        // 通过反射设置新的SQL
        FieldUtil.setFieldValue(boundSql, "sql", newSql);

        return invocation.proceed();
    }

    private BoundSql getBoundSql(MappedStatement statement, Object[] args) throws APIException {
        for (int argsIndex = 0; argsIndex < args.length; argsIndex++) {
            if (args[argsIndex] instanceof BoundSql bs) {
                return bs;
            }
        }

        return statement.getBoundSql(args[1]);
    }

    /**
     * 获取对应的SQL实体对象
     */
    private static MappedStatement getMappedStatement(Object[] args) throws APIException {
        for (int argsIndex = 0; argsIndex < args.length; argsIndex++) {
            if (args[argsIndex] instanceof MappedStatement ms) {
                return ms;
            }
        }

        return null;
    }
}